package com.caius.xiaohashu.user.biz.service.Impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.RandomUtil;
import com.caius.framework.biz.context.holder.LoginUserContextHolder;
import com.caius.framework.common.enums.DeletedEnum;
import com.caius.framework.common.enums.StatusEnum;
import com.caius.framework.common.exception.BizException;
import com.caius.framework.common.response.Response;
import com.caius.framework.common.util.JsonUtils;
import com.caius.framework.common.util.ParamUtils;
import com.caius.xiaohashu.user.biz.constant.RedisKeyConstants;
import com.caius.xiaohashu.user.biz.constant.RoleConstants;
import com.caius.xiaohashu.user.biz.domain.dataobject.RoleDO;
import com.caius.xiaohashu.user.biz.domain.dataobject.UserDO;
import com.caius.xiaohashu.user.biz.domain.dataobject.UserRoleDO;
import com.caius.xiaohashu.user.biz.domain.mapper.RoleDOMapper;
import com.caius.xiaohashu.user.biz.domain.mapper.RolePermissionDOMapper;
import com.caius.xiaohashu.user.biz.domain.mapper.UserDOMapper;
import com.caius.xiaohashu.user.biz.domain.mapper.UserRoleDOMapper;
import com.caius.xiaohashu.user.biz.enums.ResponseCodeEnum;
import com.caius.xiaohashu.user.biz.enums.SexEnum;
import com.caius.xiaohashu.user.biz.model.vo.UpdateUserInfoReqVO;
import com.caius.xiaohashu.user.biz.rcp.DistributedIdGeneratorRpcService;
import com.caius.xiaohashu.user.biz.rcp.RcpOssService;
import com.caius.xiaohashu.user.biz.service.UserService;
import com.caius.xiaohashu.user.dto.req.*;
import com.caius.xiaohashu.user.dto.resp.FindUserByIdRspDTO;
import com.caius.xiaohashu.user.dto.resp.FindUserByPhoneRspDTO;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author Caius
 * @description
 * @since Created in 2025-05-17
 */
@Service
@Slf4j
public class UserServiceImpl implements UserService {

    @Resource
    private UserDOMapper userDOMapper;

    @Resource
    private UserRoleDOMapper userRoleDOMapper;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private RcpOssService rcpOssService;

    @Resource
    private RolePermissionDOMapper rolePermissionDOMapper;

    @Resource
    private RoleDOMapper roleDOMapper;

    @Resource
    private DistributedIdGeneratorRpcService distributedIdGeneratorRpcService;

    @Resource(name = "taskExecutor")
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    private static final Cache<Long, FindUserByIdRspDTO> LOCAL_CACHE = Caffeine.newBuilder()
            .initialCapacity(10000) //初始化 10000 个条目
            .maximumSize(10000) // 最大容量为 10000 个条目
            .expireAfterWrite(1, TimeUnit.HOURS) // 设置过期时间在写入之后一个小时
            .build();

    @Resource
    private TransactionTemplate transactionTemplate;

    /**
     * 更新用户信息
     * @param updateUserInfoReqVO
     * @return
     */
    @Override
    public Response<?> updateUserInfo(UpdateUserInfoReqVO updateUserInfoReqVO) {
        UserDO userDO = new UserDO();

        userDO.setId(LoginUserContextHolder.getUserId());

        boolean needUpdate = false;

        // 头像
        MultipartFile avatarFile = updateUserInfoReqVO.getAvatar();
        if (Objects.nonNull(avatarFile)) {

            String avatar = rcpOssService.uploadFile(avatarFile);
            log.info("调用 oss 图床服务成功，url 地址为: {}", avatar);

            if (StringUtils.isBlank(avatar)) {
                throw new BizException(ResponseCodeEnum.UPLOAD_AVATAR_FAIL);
            }

            userDO.setAvatar(avatar);
            needUpdate = true;
        }

        // 昵称
        String nickname = updateUserInfoReqVO.getNickname();
        if (StringUtils.isNotBlank(nickname)) {
            Preconditions.checkArgument(ParamUtils.checkNickName(nickname), ResponseCodeEnum.NICK_NAME_VALID_FAIL.getErrorMessage());
            userDO.setNickname(nickname);
            needUpdate = true;
        }

        // 小哈书 ID
        String xiaohashuId = updateUserInfoReqVO.getXiaohashuId();
        if (StringUtils.isNotBlank(xiaohashuId)) {
            Preconditions.checkArgument(ParamUtils.checkXiaohashuId(xiaohashuId), ResponseCodeEnum.XIAOHASHU_ID_VALID_FAIL.getErrorMessage());
            userDO.setXiaohashuId(xiaohashuId);
            needUpdate = true;
        }

        // 性别
        Integer sex = updateUserInfoReqVO.getSex();
        if (Objects.nonNull(sex)) {
            Preconditions.checkArgument(SexEnum.isValid(sex), ResponseCodeEnum.SEX_VALID_FAIL);
            userDO.setSex(sex);
            needUpdate = true;
        }

        // 生日
        LocalDate birthday = updateUserInfoReqVO.getBirthday();
        if (Objects.nonNull(birthday)) {
            userDO.setBirthday(birthday);
            needUpdate = true;
        }

        // 个人简介
        String introduction = updateUserInfoReqVO.getIntroduction();
        if (StringUtils.isNotBlank(introduction)) {
            Preconditions.checkArgument(ParamUtils.checkLength(introduction, 100), ResponseCodeEnum.INTRODUCTION_VALID_FAIL);
            userDO.setIntroduction(introduction);
            needUpdate = true;
        }

        // 背景图
        MultipartFile backgroundImageFile = updateUserInfoReqVO.getBackgroundImage();
        if (Objects.nonNull(backgroundImageFile)) {

            String backgroundImage = rcpOssService.uploadFile(backgroundImageFile);

            log.info("调用 oss 图床服务成功，url 地址为: {}", backgroundImage);

            if (StringUtils.isBlank(backgroundImage)) {
                throw new BizException(ResponseCodeEnum.UPLOAD_BACKGROUNDIMAGE_FAIL);
            }

            userDO.setBackgroundImg(backgroundImage);
            needUpdate = true;
        }

        if (needUpdate == true) {
            // 更新用户信息
            userDO.setUpdateTime(LocalDateTime.now());
            userDOMapper.updateByPrimaryKeySelective(userDO);
        }

        return Response.success();
    }

    /**
     * 用户注册
     * @param requestUserReqDTO
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response<Long> register(RegisterUserReqDTO requestUserReqDTO) {
        String phone = requestUserReqDTO.getPhone();

        // 判断用户是否注册
        UserDO userDO1 = userDOMapper.selectByPhone(phone);
        log.info("用户是否注册，phone: {}, userDO: {}", phone, JsonUtils.toJsonString(userDO1));

        if (Objects.nonNull(userDO1)) {
            return Response.success(userDO1.getId());
        }

        // 自动注册用户
        // RPC: 调用分布式 ID 生成服务生成小哈书 ID
        String xiaohashuId = distributedIdGeneratorRpcService.getXiaohashuId();

        // RPC: 调用分布式 ID 生成服务生成用户 ID
        String userIdStr = distributedIdGeneratorRpcService.getUserId();
        Long userId = Long.valueOf(userIdStr);

        // 创建用户对象
        UserDO userDO = UserDO.builder()
                .id(userId)
                .phone(phone)
                .xiaohashuId(xiaohashuId)
                .nickname("小红薯" + xiaohashuId)
                .status(StatusEnum.ENABLE.getValue())
                .createTime(LocalDateTime.now())
                .updateTime(LocalDateTime.now())
                .isDeleted(DeletedEnum.NO.getValue())
                .build();

        // 添加入库
        userDOMapper.insert(userDO);

        // 分配一个默认用户角色
        UserRoleDO userRoleDO = UserRoleDO.builder()
                .userId(userId)
                .roleId(RoleConstants.COMMON_USER_ROLE_ID)
                .createTime(LocalDateTime.now())
                .updateTime(LocalDateTime.now())
                .isDeleted(DeletedEnum.NO.getValue())
                .build();
        userRoleDOMapper.insert(userRoleDO);

        RoleDO roleDO = roleDOMapper.selectByPrimaryKey(RoleConstants.COMMON_USER_ROLE_ID);

        // 将该用户的角色 ID 存入 Redis 中
        ArrayList<String> roles = new ArrayList<>(1);
        roles.add(roleDO.getRoleKey());

        String userRoleKey = RedisKeyConstants.buildUserRoleKey(userId);
        redisTemplate.opsForValue().set(userRoleKey, JsonUtils.toJsonString(roles));

        return Response.success(userId);
    }

    /**
     * 通过手机号查找用户
     * @param findUserByPhoneReqDTO
     * @return
     */
    @Override
    public Response<FindUserByPhoneRspDTO> findByPhone(FindUserByPhoneReqDTO findUserByPhoneReqDTO) {
        String phone = findUserByPhoneReqDTO.getPhone();

        UserDO userDO = userDOMapper.selectByPhone(phone);

        if (Objects.isNull(userDO)) {
            throw new BizException(ResponseCodeEnum.USER_NOT_FOUND);
        }

        FindUserByPhoneRspDTO findUserByPhoneRspDTO = FindUserByPhoneRspDTO.builder()
                .id(userDO.getId())
                .password(userDO.getPassword())
                .build();

        return Response.success(findUserByPhoneRspDTO);
    }

    /**
     * 更新用户密码
     * @param updateUserPasswordReqDTO
     * @return
     */
    @Override
    public Response<?> updateUserPassword(UpdateUserPasswordReqDTO updateUserPasswordReqDTO) {
        Long userId = LoginUserContextHolder.getUserId();

        UserDO userDO = UserDO.builder()
                .id(userId)
                .password(updateUserPasswordReqDTO.getEncodePassword())
                .updateTime(LocalDateTime.now())
                .build();

        userDOMapper.updateByPrimaryKeySelective(userDO);

        return Response.success();
    }

    /**
     * 通过用户 ID 查询用户信息
     * @param findUserByIdReqDTO
     * @return
     */
    @Override
    public Response<FindUserByIdRspDTO> findById(FindUserByIdReqDTO findUserByIdReqDTO) {
        Long userId = findUserByIdReqDTO.getId();

        // 先从本地缓存开始查询
        FindUserByIdRspDTO findUserByIdRspDTOLocalCache = LOCAL_CACHE.getIfPresent(userId);
        if (Objects.nonNull(findUserByIdRspDTOLocalCache)) {
            log.info("==> 命中了本地缓存：{}", findUserByIdRspDTOLocalCache);
            return Response.success(findUserByIdRspDTOLocalCache);
        }

        // 用户信息缓存 KEY
        String userInfoKey = RedisKeyConstants.buildUserInfoKey(userId);

        String userInfoRedisValue = (String) redisTemplate.opsForValue().get(userInfoKey);

        if (StringUtils.isNotBlank(userInfoRedisValue)) {
            // 将存储的 JSON 字符串直接返回
            FindUserByIdRspDTO findUserByIdRspDTO = JsonUtils.parseObject(userInfoRedisValue, FindUserByIdRspDTO.class);
            // 异步写入本地缓存
            threadPoolTaskExecutor.submit(()->{
                if (Objects.nonNull(findUserByIdRspDTO)) {
                    LOCAL_CACHE.put(userId, findUserByIdRspDTO);
                }
            });
            return Response.success(findUserByIdRspDTO);
        }

        UserDO userDO = userDOMapper.selectByPrimaryKey(userId);

        if (Objects.isNull(userDO)) {
            threadPoolTaskExecutor.execute(()->{
                // 防止缓存穿透，将 null 数据存入 redis 中间 (设置过期时间为 1-2 min)
                // 保底一分钟 + 随机秒数
                long expireTime = 60 + RandomUtil.randomInt(60);
                redisTemplate.opsForValue().set(userInfoKey, "null", expireTime, TimeUnit.SECONDS);
            });
            throw new BizException(ResponseCodeEnum.USER_NOT_FOUND);
        }

        FindUserByIdRspDTO findUserByIdRspDTO = FindUserByIdRspDTO.builder()
                .id(userDO.getId())
                .nickName(userDO.getNickname())
                .avatar(userDO.getAvatar())
                .introduction(userDO.getIntroduction())
                .build();

        // 异步存入用户数据提高性能
        threadPoolTaskExecutor.execute(()->{
            // 设置过期时间使用 保底一天加随机秒数 ，防止同时失效导致缓存雪崩
            long expireTime  = 60 * 60 * 24 + RandomUtil.randomInt(60 * 60 * 24);
            redisTemplate.opsForValue().set(userInfoKey, JsonUtils.toJsonString(findUserByIdRspDTO), expireTime, TimeUnit.SECONDS);
        });
        return Response.success(findUserByIdRspDTO);
    }

    /**
     * 批量查找用户信息
     * @param findUsersByIdsReqDTO
     * @return
     */
    @Override
    public Response<List<FindUserByIdRspDTO>> findUsersByIds(FindUsersByIdsReqDTO findUsersByIdsReqDTO) {
        List<Long> userIds = findUsersByIdsReqDTO.getIds();

        List<String> redisKeys = userIds.stream()
                .map(RedisKeyConstants::buildUserInfoKey)
                .toList();

        // 先从 redis 中间取值
        List<Object> redisValues = redisTemplate.opsForValue().multiGet(redisKeys);

        // 将空值过滤
        if (CollUtil.isNotEmpty(redisValues)) {
            redisValues = redisValues.stream().filter(Objects::nonNull).toList();
        }

        // 返参
        List<FindUserByIdRspDTO> findUserByIdRspDTOS = Lists.newArrayList();

        // 缓存集合中间的用户数据 -> 返参
        if (CollUtil.isNotEmpty(redisValues)) {
            findUserByIdRspDTOS = redisValues.stream()
                    .map(value -> JsonUtils.parseObject(String.valueOf(value), FindUserByIdRspDTO.class))
                    .collect(Collectors.toList());
        }

        // 请求对象数据全部在缓存中间存在
        if (CollUtil.size(userIds) == CollUtil.size(findUserByIdRspDTOS)) {
            return Response.success(findUserByIdRspDTOS);
        }

        List<Long> userIdsNeedQuery = null;

        if (CollectionUtil.isNotEmpty(findUserByIdRspDTOS)) {
            // 将 findUsersByIdsRspDTOS 转换为 map
            Map<Long, FindUserByIdRspDTO> map = findUserByIdRspDTOS.stream()
                    .collect(Collectors.toMap(FindUserByIdRspDTO::getId, p -> p));

            // 筛选需要查询 DB 的数据
            userIdsNeedQuery = userIds.stream()
                    .filter(id -> Objects.isNull(map.get(id)))
                    .toList();
        } else {
            userIdsNeedQuery = userIds;
        }

        // 调用 DB 查询需要的用户信息数据
        List<UserDO> userDOS = userDOMapper.selectByIds(userIdsNeedQuery);

        List<FindUserByIdRspDTO> findUserByIdRspDTOSRemain = null;

        // 将剩下的数据填充完毕
        if (CollUtil.isNotEmpty(userDOS)) {
            findUserByIdRspDTOSRemain = userDOS.stream()
                    .map(userDO -> FindUserByIdRspDTO.builder()
                            .id(userDO.getId())
                            .nickName(userDO.getNickname())
                            .avatar(userDO.getAvatar())
                            .introduction(userDO.getIntroduction())
                            .build())
                    .toList();

            List<FindUserByIdRspDTO> findUserByIdRspDTOSList = findUserByIdRspDTOSRemain;

            // 异步将数据存入 redis 中间
            threadPoolTaskExecutor.submit(() -> {
                // DTO-List 转换 map
                Map<Long, FindUserByIdRspDTO> map = findUserByIdRspDTOSList.stream().collect(Collectors.toMap(FindUserByIdRspDTO::getId, p -> p));

                // 执行 pipeline 操作
                redisTemplate.executePipelined(new SessionCallback<>() {
                    @Override
                    public Object execute(RedisOperations operations) throws DataAccessException {
                        for (UserDO userDO : userDOS) {
                            Long userId = userDO.getId();

                            String userInfoRedisKey = RedisKeyConstants.buildUserInfoKey(userId);

                            FindUserByIdRspDTO findUserByIdRspDTO = map.get(userId);

                            String value = JsonUtils.toJsonString(findUserByIdRspDTO);

                            long expireSeconds = 60*60*24 + RandomUtil.randomInt(60*60*24);

                            // redis 设置数据
                            operations.opsForValue().set(userInfoRedisKey, value, expireSeconds, TimeUnit.SECONDS);
                        }
                        return null;
                    }
                });
            });

        }

        // 合并数据
        if (CollUtil.isNotEmpty(findUserByIdRspDTOSRemain)) {
            findUserByIdRspDTOS.addAll(findUserByIdRspDTOSRemain);
        }

        return Response.success(findUserByIdRspDTOS);
    }
}
